{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "## RNN——aclImbd"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "3474dd139fa7b65"
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-06-11T03:04:00.906987Z",
     "start_time": "2025-06-11T03:03:56.979748Z"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "import nltk\n",
    "from sklearn.metrics import accuracy_score, precision_recall_fscore_support, confusion_matrix, classification_report\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "# 数据集路径\n",
    "aclImdb_data_dir = r'D:\\Machine_learning\\jiqixuexi\\aclImdb'\n",
    "# 自定义IMDb数据集类\n",
    "class CustomIMDbDataset(Dataset):\n",
    "    def __init__(self, data_dir, split):\n",
    "        self.data_dir = data_dir\n",
    "        self.split = split\n",
    "        self.samples = []\n",
    "        self.labels = []\n",
    "        self.tokenizer = nltk.word_tokenize\n",
    "        self.vocab = self.build_vocab()\n",
    "\n",
    "        # 加载数据\n",
    "        for label in ['pos', 'neg']:\n",
    "            label_dir = os.path.join(data_dir, split, label)\n",
    "            for file_name in os.listdir(label_dir):\n",
    "                file_path = os.path.join(label_dir, file_name)\n",
    "                with open(file_path, 'r', encoding='utf-8') as f:\n",
    "                    text = f.read()\n",
    "                self.samples.append(text)\n",
    "                self.labels.append(1 if label == 'pos' else 0)\n",
    "\n",
    "    def build_vocab(self):\n",
    "        vocab = {}\n",
    "        for label in ['pos', 'neg']:\n",
    "            label_dir = os.path.join(self.data_dir, self.split, label)\n",
    "            for file_name in os.listdir(label_dir):\n",
    "                file_path = os.path.join(label_dir, file_name)\n",
    "                with open(file_path, 'r', encoding='utf-8') as f:\n",
    "                    text = f.read()\n",
    "                tokens = self.tokenizer(text.lower())\n",
    "                for token in tokens:\n",
    "                    if token not in vocab:\n",
    "                        vocab[token] = len(vocab)\n",
    "        return vocab\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.samples)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        text = self.samples[idx]\n",
    "        label = self.labels[idx]\n",
    "\n",
    "        # 文本处理\n",
    "        tokens = self.tokenizer(text.lower())\n",
    "        indices = [self.vocab[token] if token in self.vocab else len(self.vocab) for token in tokens]\n",
    "        tensor = torch.tensor(indices, dtype=torch.long)\n",
    "\n",
    "        return tensor, label\n",
    "# 定义RNN模型用于文本分类\n",
    "class TextRNN(nn.Module):\n",
    "    def __init__(self, vocab_size, embedding_dim, hidden_dim, num_layers, num_classes):\n",
    "        super(TextRNN, self).__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size + 1, embedding_dim)\n",
    "        self.rnn = nn.RNN(\n",
    "            input_size=embedding_dim,\n",
    "            hidden_size=hidden_dim,\n",
    "            num_layers=num_layers,\n",
    "            batch_first=True,\n",
    "            bidirectional=False\n",
    "        )\n",
    "        self.dropout = nn.Dropout(0.5)\n",
    "        self.fc = nn.Linear(hidden_dim, num_classes)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # x: (batch_size, seq_len)\n",
    "        x = self.embedding(x)  # (batch_size, seq_len, embedding_dim)\n",
    "        output, _ = self.rnn(x)  # (batch_size, seq_len, hidden_dim)\n",
    "        output = output[:, -1, :]  # 获取最后一个时间步的输出 (batch_size, hidden_dim)\n",
    "        output = self.dropout(output)\n",
    "        output = self.fc(output)\n",
    "        return output\n",
    "\n",
    "# 加载数据\n",
    "def load_data(data_dir, split, batch_size):\n",
    "    dataset = CustomIMDbDataset(data_dir, split)\n",
    "    data_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True, collate_fn=lambda batch: batch)\n",
    "    return data_loader"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-06-11T03:04:02.592424Z",
     "start_time": "2025-06-11T03:04:02.577342Z"
    }
   },
   "id": "ea18be3a64e69aa8",
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [1/10], Loss: 0.7015\n",
      "Epoch [2/10], Loss: 0.6950\n",
      "Epoch [3/10], Loss: 0.6940\n",
      "Epoch [4/10], Loss: 0.6940\n",
      "Epoch [5/10], Loss: 0.6974\n",
      "Epoch [6/10], Loss: 0.6966\n",
      "Epoch [7/10], Loss: 0.6987\n",
      "Epoch [8/10], Loss: 0.6988\n",
      "Epoch [9/10], Loss: 0.6982\n",
      "Epoch [10/10], Loss: 0.6974\n",
      "Training Time: 354.72 seconds\n",
      "Inference Time: 34.48 seconds\n",
      "Training Time: 354.72 seconds\n",
      "Inference Time: 34.48 seconds\n",
      "Accuracy: 0.4992\n",
      "Precision: 0.4680\n",
      "Recall: 0.4992\n",
      "F1-score: 0.3380\n",
      "Confusion Matrix:\n",
      " [[   72 12428]\n",
      " [   93 12407]]\n",
      "Classification Report:\n",
      "               precision    recall  f1-score   support\n",
      "\n",
      "           0       0.44      0.01      0.01     12500\n",
      "           1       0.50      0.99      0.66     12500\n",
      "\n",
      "    accuracy                           0.50     25000\n",
      "   macro avg       0.47      0.50      0.34     25000\n",
      "weighted avg       0.47      0.50      0.34     25000\n"
     ]
    },
    {
     "data": {
      "text/plain": "<Figure size 800x600 with 2 Axes>",
      "image/png": "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kxYtWqSQkBC1aNHihtewceNGderU6ZbXKkl33XWXHnnkkXzX+9VXXxW6EP2z1q1bS5K+//57m/Y333xTnTp10sKFC+Xt7a0RI0bk23vauXNnxcXF5StAAZRu7InE31p0dLR1f9d1FotFvr6+mjt3rjU9+rO77rpLkydPVmhoqBYuXKgRI0bc9DOMjjfqwoULkqR77723UON37typPXv26O2331b37t0lSY888ohcXV01d+5cvfDCC9a9iNnZ2Vq2bJl1WTk1NVVjxozRTz/9pAcffNA6ztvbWw0aNCj0nPfv36+WLVta067AwEC5u7vLy8urwPHvvvuuatWqpaVLl6pMmTKSpFatWikoKEjz58/XnDlzrGN79+6tIUOGSJJatGih7du3a8eOHXrmmWduOa/OnTvrgw8+UFZWljWJ3rRpk15++eV8Y2NiYtSjRw+NHz/e2ubv76/AwEBFR0erSZMm1iXh2rVr23w/2dnZmjx5su67774C51G5cmVNmjRJr776qtasWaOPP/5Y999/v1577bUbzv3UqVNKSEgwVPx17txZb7zxhi5duqTKlStL+qNoNpJCXlelShVJUkJCgk37kCFDNHDgQEnSo48+ql9//VULFiyw2Xt63333ydPTU3v37i1wTyqA0okkEn9rDRs21Nq1a7V27VotXLhQvr6+qlOnjmbPnn3TRKdt27bq3r27li5dqmPHjt3yc4yOl/64KefPKemfl5r/zMnpj39Nb9T/V/v371eZMmXUpUsXm/brBeWfl5C9vb1t9iVWq1ZNkpSenl6oz7qRwMBArVmzRgMGDNC///1vnTt3TkOGDCkwTU1LS9PRo0fVpUsXawEp/VGct2nTJt8d3P7+/javq1evrrS0tELNq0uXLrpy5Yp1Sfvw4cOKi4tThw4d8o3t37+/Zs2apbS0NB0/flybN2/W+++/L+mPLQY34+rqqtq1a990TKdOndS1a1dNnDhRv/76q955552b3pwVGxsrqfB/mZCk9u3bq0yZMtYl7fj4eB04cOC2isjrLBaLzeu/bgNo3769Dh06pNTUVJv2mjVrGrrzHUDJRxGJv7Xy5curUaNGatSokdq3b6/IyEilpKToxRdfLPCGgz8LCwuTl5eXxo4dW6hlaqPjg4KC1LBhQ+vxxhtvFDjunnvukSSdO3fuhudKTEzUtWvXJElXrlyRl5dXviXU68u1ycnJ1jY3NzebMUYL1hsZP368hg8frt9++02TJ09W27Zt9cwzz+h///tfvrHJycnKy8uzJl1/VqVKFZv5Sn8UaH+dc2Gf0/iPf/xD9evX15YtWyT9kUK2atWqwLvaExMT9corryggIEDBwcGaN2+erl69KunWz4WsXLlyvmKrID169FBubq7uu+8+1a1b96Zjr38Pf/1ndjMeHh569NFHrUvaW7Zskbe3t3x9fQt9juvi4uIkKd/NQX/dBlC5cmXl5eXle+yUm5ubQx9FBeDOo4iEqVSuXFkTJkzQhQsXFB4eftOxnp6emjRpkn7++WdFRETc8txGx0dERFhT0rVr12ro0KEFjqtfv76qVKminTt33vBckyZNUuvWrZWRkSFPT09dvnw5380m8fHxknTDJWUj/nqH7l+TQBcXF4WGhmrz5s365ptvNGHCBMXGxur111/Pd64KFSrIYrHo4sWL+foSEhJUsWJFu+f7Z126dNH27duVmZmpLVu23DCVGzlypI4cOaIVK1bo0KFD2rx5s83Str0yMjIUHh4uX19fnTp1SkuWLLnp+Ov/3K4XsoXVpUsXHThwQBcvXtSmTZtuO4W8nt42a9bMpv3KlSs2ry9evKgyZcrkK8yvXr1a5P8sARQvikiYTocOHdS6dWt98cUXt3zYdfv27fXEE0/o/fffv2VyaXR8vXr1rClpo0aNbrhM6eTkpJCQEO3YsUNff/11vv7o6Gj997//VceOHeXq6qrmzZsrJycn312yGzZskCQFBATc8jpuxsPDw7pP87offvjB+ueMjAx17NjRejd2zZo11adPH3Xt2jXf+yTJ3d1dDz74oDZt2mRTnCYnJ2vHjh12z/evOnfurKtXr2rRokW6cuVKgftipT9uIOnYsaMefvhh6zLz9UL+elL75+V3o959912dP3/euk91wYIFOnHixA3H16xZU5IK/A5vpk2bNipXrpw++ugjHTp06LaKyJSUFC1fvlz16tVTkyZNbPp27dpl/XNubq62bNmixo0b2yTGeXl5iouLs6bqAP4euLEGpvTGG2+oe/fumjZtmtatW5dv6ffP3nzzTUVFRRWYlBXF+MIICQlRdHS0hg0bpt69e+vxxx+Xk5OTDhw4oI8++kg+Pj4aM2aMpD9ubggMDNTEiRMVHx+vBg0aaP/+/VqyZIn++c9/2v2A7zZt2mjx4sV677335Ofnpx07dtg8lsbV1VUNGzbUggUL5OzsrHr16umXX37RunXr1LFjxwLP+frrr+ull15S//799fzzzysrK0vvv/++MjMzb5jQ3q5atWqpUaNGWrp0qYKCglS+fPkCxz300EPauHGjGjZsqOrVq+vgwYNavHixLBaLdc9ohQoVJEl79+5V3bp11bhx40LNITo6Wh999JGGDx+u+++/X6+88oq++uorjR07Vp988kmBj5+6//77VbNmTf3www8FPurpRtzd3fXYY49p2bJleuihh1SrVq2bjo+JibE+FPzatWs6ffq0PvroI12+fFlz587Nt0w/Z84c5eTkqEaNGvrPf/6jX375RStWrLAZc+LECSUnJ1vv8Abw90ARCVO6//771bdvXy1fvlwff/yxQkJCbji2YsWKmjRpUqGLGaPjC8PZ2VmLFi3S6tWrtX79em3evFmZmZm69957NWjQIPXt29daDFksFi1evFjz5s3Thx9+qMTERN17770aMWKE+vXrZ/dcBg0apMTERC1fvlxZWVl6/PHHFR4ertDQUOuYKVOmaM6cOVq+fLkSEhJUuXJl9erVS6+++mqB52zRooVWrFihefPm6bXXXpOLi4uaNm2qWbNm2Tz/sqh06dJFR48evWkqN3PmTE2dOlVTp06VJNWpU0eTJ0/Whg0bdODAAUl/pLL9+vXT6tWrtWPHDu3evfuWn52WlqZx48bJ19dXL730kqQ/Cr2JEydq4MCBioiI0LBhwwp8b8eOHa2PTjJ6vVu2bMl3s1VBpkyZYv2zu7u77r77brVq1UohISEFFqDh4eF66623dObMGfn6+mrJkiVq3ry5zZidO3eqatWq+VJMAKWbJa+wO9IBAMUqLi5OQUFBWr58uZo2bVrc0ymUvLw8dejQQX369LnpX9YAlD7siQSAUqJatWp64YUXrI8aKg02b96s3NzcQj3HE0DpQhEJAKXIK6+8ori4OJsbWkqqzMxMzZ49W7Nmzcr3aCYApR/L2QAAADCMJBIAAACGUUQCAADAMIpIAAAAGEYRCQAAAMP+Fg8bz8i+9RgApZNXs6L9xRoAJUf6wQXF9tlu/o77/5bivK47iSQSAAAAhv0tkkgAAABDLORo9qKIBAAA5mOxFPcMSj3KcAAAABhGEgkAAMyH5Wy78Q0CAADAMJJIAABg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8/NTq1atNGvWLOXm5hbZ5/Tt21fz58+XJE2YMEETJky45Xv++9//6qWXXrrtz/zss8/Utm3bAvv27dunevXq3fa569Wrp3379t3We+fPn6++ffve9mcDQElXtrgnAODOmDx5soKDg62vT5w4oZCQELm5uWnYsGFF/nlTpkwp1LikpCTxw1kAUPqQRAImVa9ePTVr1kz/+9//JP2RIo4dO1Zt2rTR448/rpSUFJ09e1Yvv/yyAgMD1aZNG82ePVuZmZnWc6xZs0bt2rWTv7+/xowZo/T0dGvf2LFjNXbsWOvrDz74QEFBQfL391dwcLD27t2rffv2aeLEiTp//rz8/f0VFxenzMxMzZ07V+3atVPz5s01YMAAnTlzxnqeU6dOqW/fvvL391e3bt2s878dcXFxGj58uNq2bavGjRurXbt2Wrt2rc2Y7777Tp07d1ZgYKCGDRumhIQEa9+xY8fUt29fNWvWTB06dFBkZCQFMQDToIgETCgrK0v79u1TVFSUHnnkEWv7nj17tGrVKm3YsEFOTk4KCQmRj4+Pdu7cqX//+9/as2ePdbl67969mjJliqZNm6bo6Gg1btxYR48eLfDzPvvsMy1atEhvvfWWvv/+ez377LMKDQ1VvXr1NHnyZNWsWVMHDx5UtWrVNHv2bO3YsUORkZHatWuXGjdurBdffFHXrl1TVlaWBg0aJB8fH0VFRelf//qXtm/fftvfQ1hYmJydnfXll1/qhx9+0PPPP6+pU6cqNTXVOubbb7/V0qVL9fXXXysrK0sjR46U9EcB+sILL6hTp07as2ePFi1apH//+99avXr1bc8HAEoTikjAJCZPnqymTZuqadOmatGihaZOnap+/frp+eeft4559NFHVa1aNd11113asWOHMjMz9dprr6lcuXKqUaOGXn31Va1cuVKStGHDBnXo0EEtWrRQ2bJl9dxzz6lBgwYFfva6dev09NNPy9/fX05OTurdu7eWL18uV1dXm3F5eXlatWqVXnvtNdWqVUvlypXTkCFDlJWVpR07dujgwYP6/fffNXr0aJUrV04+Pj7q16/fbX8n06ZN08SJE+Xs7Kzz58+rfPnyysjI0JUrV6xjhg0bpnvuuUceHh4aPXq0oqKiFBcXpw0bNqhu3brq06ePnJ2d5e3trZdeesn6/QDA3x17IgGTmDhxos2eyILcfffd1j+fO3dOiYmJatasmbUtLy9PWVlZunTpkuLi4tSwYUOb99eqVavA8yYkJKhmzZo2bU2aNMk3LjExUWlpaXr11Vfl5PT//x03KytL586dU2Zmpry8vGyKz9q1a9/0mm4mNjZWb731ln799VfVqVNH9913nyTZ3Gx07733Wv98/Rri4uJ07tw5HTt2TE2bNrX25+bmqkyZMrc9HwAoTSgiAVhZLBbrn6tXr67atWtry5Yt1raUlBRdunRJlSpVUvXq1RUbG2vz/gsXLsjHxyffeWvUqKHff//dpm327Nnq3r27TZuXl5fKlSun5cuXy8/Pz9p++vRpVatWTT/99JMSExOVmpqq8uXLWz/zdlxfGn/ttdf03HPPyWKx6Mcff9SGDRtsxsXHx+uBBx6QJOv13nvvvapevboCAwO1bNky69jLly/bLIUDwN8Zy9kACtSmTRulpqZq6dKlyszM1NWrVzVmzBiNGDFCFotFPXv21Pbt2/XNN98oOztb69at0+HDhws8V3BwsFavXq0jR44oNzdXn376qVauXGktGtPT05WdnS0nJyf16tVL7777ri5cuKDc3FytW7dOTzzxhM6cOSN/f3/94x//0LRp05Senq4zZ85o+fLlt7yWCxcu2Bzx8fHKyspSRkaGXF1dZbFYdP78eb399tuS/igwr5s/f77i4uJ05coVzZw5Ux06dFClSpXUrVs3HTp0SBs2bFB2drbi4+P18ssva+bMmUXzDwAASjiSSAAF8vDwUGRkpGbOnKmlS5cqNzdXgYGBioiIkCQFBATorbfe0syZMzVixAg9/PDDNjfp/Fm3bt109epVjRo1SgkJCfL29taSJUtUqVIlNWvWTJUrV1azZs20atUqjRkzRvPnz9dzzz2npKQk1apVS/PmzbPut3z//fc1YcIEtWzZUlWqVFG7du20devWm17LY489ZvO6SpUq2r17t6ZPn665c+dq2rRpqly5sp566inFxMTo559/1j/+8Q9JUuvWrfXUU08pIyNDbdq00RtvvCFJuueee7R06VK98847mjZtmsqUKaPHH39c48ePt+t7B4DSwpLH8ygAAABgEMvZAAAAMIwiEgAAAIZRRAIAAMAwikgAAAAYRhEJAAAAwygiAQAAYBhFJAAAAAyjiAQAAIBhFJEAAAAwjCISAAAAhlFEAgAAwLD/D09SBJj6Kz0BAAAAAElFTkSuQmCC"
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model saved successfully!\n"
     ]
    }
   ],
   "source": [
    "# 主函数\n",
    "def main():\n",
    "    # 参数设置\n",
    "    batch_size = 64\n",
    "    embedding_dim = 100\n",
    "    hidden_dim = 128\n",
    "    num_layers = 2\n",
    "    num_classes = 2\n",
    "    num_epochs = 10\n",
    "\n",
    "    # 加载数据\n",
    "    train_loader = load_data(aclImdb_data_dir, 'train', batch_size)\n",
    "    test_loader = load_data(aclImdb_data_dir, 'test', batch_size)\n",
    "\n",
    "    # 构建词汇表并获取词汇表大小\n",
    "    dataset = CustomIMDbDataset(aclImdb_data_dir, 'train')\n",
    "    vocab_size = len(dataset.vocab)\n",
    "\n",
    "    # 初始化模型、损失函数和优化器\n",
    "    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "    model = TextRNN(vocab_size, embedding_dim, hidden_dim, num_layers, num_classes).to(device)\n",
    "    criterion = nn.CrossEntropyLoss()\n",
    "    optimizer = optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "    # 训练模型\n",
    "    start_time = time.time()\n",
    "\n",
    "    for epoch in range(num_epochs):\n",
    "        model.train()\n",
    "        epoch_loss = 0.0\n",
    "        for batch in train_loader:\n",
    "            # 处理批次数据\n",
    "            batch_data = []\n",
    "            batch_labels = []\n",
    "            for item in batch:\n",
    "                text, label = item\n",
    "                batch_data.append(text)\n",
    "                batch_labels.append(label)\n",
    "            # 填充文本数据以使它们具有相同的长度\n",
    "            max_len = max(len(text) for text in batch_data)\n",
    "            padded_batch = []\n",
    "            for text in batch_data:\n",
    "                padded_text = text.tolist() + [vocab_size] * (max_len - len(text))\n",
    "                padded_batch.append(padded_text)\n",
    "            batch_data = torch.tensor(padded_batch, dtype=torch.long).to(device)\n",
    "            batch_labels = torch.tensor(batch_labels, dtype=torch.long).to(device)\n",
    "\n",
    "            optimizer.zero_grad()\n",
    "            outputs = model(batch_data)\n",
    "            loss = criterion(outputs, batch_labels)\n",
    "            epoch_loss += loss.item()\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "\n",
    "        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss / len(train_loader):.4f}')\n",
    "\n",
    "    train_time = time.time() - start_time\n",
    "    print(f\"Training Time: {train_time:.2f} seconds\")\n",
    "\n",
    "    # 测试模型\n",
    "    model.eval()\n",
    "    start_inference = time.time()\n",
    "\n",
    "    all_labels = []\n",
    "    all_preds = []\n",
    "    with torch.no_grad():\n",
    "        for batch in test_loader:\n",
    "            batch_data = []\n",
    "            batch_labels = []\n",
    "            for item in batch:\n",
    "                text, label = item\n",
    "                batch_data.append(text)\n",
    "                batch_labels.append(label)\n",
    "            max_len = max(len(text) for text in batch_data)\n",
    "            padded_batch = []\n",
    "            for text in batch_data:\n",
    "                padded_text = text.tolist() + [vocab_size] * (max_len - len(text))\n",
    "                padded_batch.append(padded_text)\n",
    "            batch_data = torch.tensor(padded_batch, dtype=torch.long).to(device)\n",
    "            batch_labels = torch.tensor(batch_labels, dtype=torch.long).to(device)\n",
    "\n",
    "            outputs = model(batch_data)\n",
    "            _, predicted = torch.max(outputs.data, 1)\n",
    "            all_labels.extend(batch_labels.cpu().numpy())\n",
    "            all_preds.extend(predicted.cpu().numpy())\n",
    "\n",
    "    inference_time = time.time() - start_inference\n",
    "    print(f\"Inference Time: {inference_time:.2f} seconds\")\n",
    "\n",
    "    # 计算评估指标\n",
    "    accuracy = accuracy_score(all_labels, all_preds)\n",
    "    precision, recall, f1, _ = precision_recall_fscore_support(all_labels, all_preds, average='weighted')\n",
    "    conf_matrix = confusion_matrix(all_labels, all_preds)\n",
    "    class_report = classification_report(all_labels, all_preds)\n",
    "\n",
    "    # 输出结果\n",
    "    print(f\"Training Time: {train_time:.2f} seconds\")\n",
    "    print(f\"Inference Time: {inference_time:.2f} seconds\")\n",
    "    print(f\"Accuracy: {accuracy:.4f}\")\n",
    "    print(f\"Precision: {precision:.4f}\")\n",
    "    print(f\"Recall: {recall:.4f}\")\n",
    "    print(f\"F1-score: {f1:.4f}\")\n",
    "    print(\"Confusion Matrix:\\n\", conf_matrix)\n",
    "    print(\"Classification Report:\\n\", class_report)\n",
    "\n",
    "    # 绘制混淆矩阵热力图\n",
    "    plt.figure(figsize=(8, 6))\n",
    "    sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues')\n",
    "    plt.title(\"RNN - Confusion Matrix (IMDb)\")\n",
    "    plt.xlabel(\"Predicted Label\")\n",
    "    plt.ylabel(\"True Label\")\n",
    "    plt.show()\n",
    "\n",
    "    # 保存模型\n",
    "    torch.save(model.state_dict(), 'rnn_model.pth')\n",
    "    print('Model saved successfully!')\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-06-11T03:12:19.749891Z",
     "start_time": "2025-06-11T03:04:03.596655Z"
    }
   },
   "id": "da8d665dccefa1f8",
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false
   },
   "id": "b02d14f678e8b1c5"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
